Build a Weather Station with Elixir and Nerves by Alexander Koutmos & Bruce Tate & Frank Hunleth

Build a Weather Station with Elixir and Nerves by Alexander Koutmos & Bruce Tate & Frank Hunleth

Author:Alexander Koutmos & Bruce Tate & Frank Hunleth [Alexander Koutmos]
Language: eng
Format: epub
Publisher: Pragmatic Bookshelf
Published: 2022-01-05T16:00:00+00:00


Wrap the Core and Hardware in a GenServer

It’s customary to wrap sensors in OTP GenServers because there are often requirements to read from them periodically to get consistent readings. In our case, the wrapping is also practical because the code must wait between readings. The amount of time is specified in the sensor’s spec sheet. By periodically taking measurements every second, we’ll be fine. Let’s build a GenServer to do so.

Since the GenServer will also double as the API for the whole project, we can put the GenServer in the top-level lib/veml6030.ex file. Go ahead and delete the module code created by mix new and start with the GenServer boilerplate:

​ ​defmodule​ VEML6030 ​do​

​ ​use​ GenServer

​

​ ​require​ Logger

​

​ alias VEML6030.{Comm, Config}

​ ​end​

Here we use, require, and alias various dependencies as needed. Next, add two versions of the init/1 GenServer callback, like so:

​ ​defmodule​ VEML6030 ​do​

​ ...

​

​ @impl true

​ ​def​ init(%{​address:​ address, ​i2c_bus_name:​ bus_name} = args) ​do​

​ i2c = Comm.open(bus_name)

​

​ config =

​ args

​ |> Map.take([​:gain​, ​:int_time​, ​:shutdown​, ​:interrupt​])

​ |> Config.new()

​

​ Comm.write_config(config, i2c, address)

​ ​:timer​.send_interval(1_000, ​:measure​)

​

​ state = %{

​ ​i2c:​ i2c,

​ ​address:​ address,

​ ​config:​ config,

​ ​last_reading:​ ​:no_reading​

​ }

​

​ {​:ok​, state}

​ ​end​

​ ​end​

This version creates a configuration and opens the I2C bus. It also sends a periodic message to itself at a one-second interval. Then it sets the state of the GenServer.

Now we can write an alternative init to discover the address and bus if they’re not set, like this:

​ ​defmodule​ VEML6030 ​do​

​ ...

​

​ ​def​ init(args) ​do​

​ {bus_name, address} = Comm.discover()

​ transport = ​"​​bus: ​​#{​bus_name​}​​, address: ​​#{​address​}​​"​

​

​ Logger.info(​"​​Starting VEML6030. Please specify an address and a bus."​)

​ Logger.info(​"​​Starting on "​ <> transport)

​

​ defaults =

​ args

​ |> Map.put(​:address​, address)

​ |> Map.put(​:i2c_bus_name​, bus_name)

​

​ init(defaults)

​ ​end​

​ ​end​

If either the address or bus is not there, the code automatically discovers the sensor, creates a set of default configurations, and calls our prior init/1 implementation. Next, we’ll write the GenServer callbacks to handle our :measure message that’s sent at a regular one-second interval and the :get_measurement message that returns the GenServer’s last measurement reading:

​ ​defmodule​ VEML6030 ​do​

​ ...

​

​ @impl true

​ ​def​ handle_info(

​ ​:measure​,

​ %{​i2c:​ i2c, ​address:​ address, ​config:​ config} = state

​ ) ​do​

​ last_reading = Comm.read(i2c, address, config)

​ updated_with_reading = %{state | ​last_reading:​ last_reading}

​

​ {​:noreply​, updated_with_reading}

​ ​end​

​

​ @impl true

​ ​def​ handle_call(​:get_measurement​, _from, state) ​do​

​ {​:reply​, state.last_reading, state}

​ ​end​

​ ​end​

The handle_info/2 callback, which handles the :measure message, updates the :last_reading key in our GenServer state based on the response from our Comm module. This updated GenServer state now contains the latest light measurement from the VEML6030 sensor.

To make this data easily accessible, we’ll also want to add a couple of public API functions:

​ ​defmodule​ VEML6030 ​do​

​ ...

​

​ ​def​ start_link(options \ %{}) ​do​

​ GenServer.start_link(__MODULE__, options, ​name:​ __MODULE__)

​ ​end​

​

​ ​def​ get_measurement ​do​

​ GenServer.call(__MODULE__, ​:get_measurement​)

​ ​end​

​

​ ...

​ ​end​

Our start_link/1 function wraps the GenServer start_link/3 function and sets the name of the GenServer process to VEML6030 (the name of the module). The get_measurement/0 function is another GenServer wrapper that makes it easier to fetch the light measurement from the GenServer process. Since we gave our



Download



Copyright Disclaimer:
This site does not store any files on its server. We only index and link to content provided by other sites. Please contact the content providers to delete copyright contents if any and email us, we'll remove relevant links or contents immediately.